package model

import (
	"errors"
	"fmt"
	"github.com/jinzhu/gorm"
)

const (
	DefaultLedger LedgerType = 1 << iota
	CustomLedger
)

var (
	ErrLedgerNotFound = errors.New("无法找到台帐信息")
)

type (
	LedgerType int8

	Ledger struct {
		Model
		Type         LedgerType `json:"type" gorm:"column:type;type:int;not null;default:1"`
		Name         string     `json:"name" gorm:"column:name;size:255;not null"`
		Description  string     `json:"description" gorm:"column:name;type:text;not null;default:'';"`
		MainDeviceNo string     `json:"main_device_no" gorm:"column:main_device_no;not null;"`
		MainDeviceID uint       `json:"main_device_id" gorm:"column:main_device_id;not null;"`
		Rules        string     `json:"rules" gorm:"rules;type:text"` // FIXME: 规则占位

		Devices LedgerDevices `json:"devices" gorm:"-"`
	}
)

func GetLedgers(index, size int) ([]*Ledger, int, error) {
	ledgers := make([]*Ledger, 0)
	total := 0

	if err := db.Model(&Ledger{}).Count(&total).Error; err != nil {
		return nil, -1, err
	}
	if err := db.Model(&Ledger{}).Offset((index - 1) * size).Limit(size).Find(&ledgers).Error; err != nil {
		return nil, -1, err
	}

	return ledgers, total, nil
}

func GetLedger(id int) (*Ledger, error) {
	ledger := new(Ledger)

	if err := db.Model(Ledger{}).First(ledger, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrLedgerNotFound
		}
	}

	ledgerDevices, err := GetLedgerDevicesByLedgerID(uint(id))
	if err != nil {
		return nil, err
	}
	ledger.Devices = ledgerDevices

	return ledger, nil
}

func AddLedger(ledger *Ledger) error {
	exist := 0
	if err := db.Where("name = ?", ledger.Name).Count(&exist).Error; err != nil {
		return err
	}

	if exist != 0 {
		return fmt.Errorf("名称为%s的台帐已存在", ledger.Name)
	}

	if ledger.Type == CustomLedger && ledger.MainDeviceID == 0 {
		ledger.MainDeviceID = ledger.Devices[0].DeviceID
	}

	device, err := GetDevice(int(ledger.MainDeviceID))
	if err != nil {
		return err
	}
	ledger.MainDeviceNo = device.DeviceNo

	tx := db.Begin()
	if err := tx.Create(ledger).Error; err != nil {
		tx.Rollback()
		return err
	}

	for _, ld := range ledger.Devices {
		ld.LedgerID = ledger.ID
	}

	if err := AddLedgerDevices(ledger.Devices, tx); err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}

func UpdatedLedger(id int, change *Ledger) error {
	oldLedger, err := GetLedger(id)
	if err != nil {
		return err
	}

	if oldLedger.MainDeviceID != change.MainDeviceID {
		device, err := GetDevice(int(change.MainDeviceID))
		if err != nil {
			return err
		}
		change.MainDeviceNo = device.DeviceNo
		change.MainDeviceID = device.ID
	}

	newRel := make(LedgerDevices, 0)
	delRel := make(LedgerDevices, 0)

	oldRelMap := oldLedger.Devices.ToMap()
	newRelMap := change.Devices.ToMap()

	for k, rel := range newRelMap {
		if _, ok := oldRelMap[k]; ok {
			delete(oldRelMap, k)
			continue
		}
		newRel = append(newRel, rel)
	}

	for _, rel := range oldRelMap {
		delRel = append(delRel, rel)
	}

	tx := db.Begin()

	for _, rel := range newRel {
		rel.LedgerID = oldLedger.ID
		if err := tx.Create(rel).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	for _, rel := range delRel {
		if err := tx.Delete(rel).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	if err := tx.Model(oldLedger).Updates(change).Error; err != nil {
		tx.Rollback()
		return err
	}

	return nil
}

func DeleteLedger(id int) error {
	ledger, err := GetLedger(id)
	if err != nil {
		return err
	}

	tx := db.Begin()

	if err := DeleteLedgerDevicesByLedgerID(id, tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Delete(ledger).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}
